﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TinyEngine.Core.Components;
using TinyEngine.Core.Entities;
using TinyEngine.Core.Services;
using TinyEngine.Core;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework;
using TinyGameSample.Common;
using TinyEngine.Entities;

namespace PlatformerGame.Components
{
    public class CharacterControllerComponent : TinyComponentBase, ICopyable
    {
        AnimatedSpriteEntity spriteOwner;
        InputService inputService;
        TileGridEntity tileGridWorld;

        public CharacterControllerComponent(IEntity owner)
            : base(owner)
        {
            this.spriteOwner = owner as AnimatedSpriteEntity;
            inputService = ServiceManager.GetService<InputService>();
        }

        Keys leftKey = Keys.Left;
        Keys rightKey = Keys.Right;
        Keys jumpKey = Keys.Space;
        float inputMovement = 0;
        bool isJumping = false;

        public override void Update(float elapsed)
        {
            UpdateInput(elapsed);

            ApplyPhysics(elapsed);

            if (isOnGround)
            {
                if (Math.Abs(velocity.X) - 0.02f > 0)
                {
                    if (spriteOwner.CurrentAnimation.Name == "Idle")
                    {
                        spriteOwner.PlayAnimation("Run");
                        spriteOwner.FlipHorizontal = velocity.X > 0;
                    }
                }
                else
                {
                    if (spriteOwner.CurrentAnimation.Name != "Idle")
                        spriteOwner.PlayAnimation("Idle");
                }
            }


            inputMovement = 0;
            isJumping = false;
        }
        float previousBottom;
        private void HandleCollisions()
        {
            var pos = Owner.Position;
            //offset the position with the tile grid.
            pos.X += tileGridWorld.Origin.X;
            pos.Y += tileGridWorld.Origin.Y;

            pos.X -= spriteOwner.BoundingRectangle.Width/2;
            pos.Y -= spriteOwner.BoundingRectangle.Height/2;

            var bounds = new Rectangle((int)pos.X, (int)pos.Y, spriteOwner.BoundingRectangle.Width, spriteOwner.BoundingRectangle.Height);
            int leftTile = (int)Math.Floor((float)bounds.Left / tileGridWorld.TileSizeX);
            int rightTile = (int)Math.Ceiling((float)bounds.Right / tileGridWorld.TileSizeX) - 1;
            int topTile = (int)Math.Floor((float)bounds.Top / tileGridWorld.TileSizeY);
            int bottomTile = (int)Math.Ceiling((float)bounds.Bottom / tileGridWorld.TileSizeY) - 1;

            isOnGround = false;

            for (int y = topTile; y <= bottomTile; y++)
            {
                for (int x = leftTile; x <= rightTile; x++)
                {
                    TileCollision collision = GetTileCollision(x, y);
                    if (collision != TileCollision.Passable)
                    {
                        // Determine collision depth (with direction) and magnitude.
                        Rectangle tileBounds = tileGridWorld.GetTilePosition(x, y);
                        tileBounds.Offset((int)tileGridWorld.Origin.X, (int)tileGridWorld.Origin.Y);
                        Vector2 depth = RectangleExtensions.GetIntersectionDepth(bounds, tileBounds);
                        if (depth != Vector2.Zero)
                        {
                            float absDepthX = Math.Abs(depth.X);
                            float absDepthY = Math.Abs(depth.Y);

                            // Resolve the collision along the shallow axis.
                            if (absDepthY <=absDepthX || collision == TileCollision.Platform)
                            {
                                // If we crossed the top of a tile, we are on the ground.
                                if (previousBottom <= tileBounds.Top)
                                    isOnGround = true;

                                // Ignore platforms, unless we are on the ground.
                                if (collision == TileCollision.Impassable || isOnGround)
                                {
                                    // Resolve the collision along the Y axis.
                                    Owner.Position = new Vector2(Owner.Position.X, Owner.Position.Y + depth.Y).ToVector3();
                                    
                                    // Perform further collisions with the new bounds.
                                    bounds = Owner.BoundingRectangle;
                                    bounds.X += (int)tileGridWorld.Origin.X;
                                    bounds.Y += (int)tileGridWorld.Origin.Y;

                                }
                            }
                            else if (collision == TileCollision.Impassable) // Ignore platforms.
                            {
                                // Resolve the collision along the X axis.
                                Owner.Position = new Vector2(Owner.Position.X + depth.X, Owner.Position.Y).ToVector3();

                                // Perform further collisions with the new bounds.
                                bounds = Owner.BoundingRectangle;
                                bounds.X += (int)tileGridWorld.Origin.X;
                                bounds.Y += (int)tileGridWorld.Origin.Y;
                            }
                        }
                    }
                }
            }

            previousBottom = bounds.Bottom;
            

        }

        private TileCollision GetTileCollision(int x, int y)
        {
            if (x < 0 || x >= tileGridWorld.TilesX)
                return TileCollision.Impassable;

            if (y < 0 || y >= tileGridWorld.TilesY)
                return TileCollision.Impassable;

            return GetCollisionValueForTile(x, y);
        }

        public void Reset()
        {
            spriteOwner.PlayAnimation("Idle");
        }

        private TileCollision GetCollisionValueForTile(int x, int y)
        {
            var index = TileGridEntity.GetTileIndex(x, y, tileGridWorld.TilesX);
            var tile = tileGridWorld.Tiles[index];
            if (tile == null)
                return TileCollision.Passable;

            switch (tile.ImageIndex)
            {
                case 0:
                case 1:
                case 2:
                case 3:
                case 4:
                case 5:
                case 6:
                case 7:
                case 8:
                    return TileCollision.Impassable;
                case 9:
                    return TileCollision.Platform;
                default:
                    return TileCollision.Passable;
            }

        }

        private bool isOnGround;
        Vector3 velocity = Vector3.Zero;
        private const float MoveAcceleration = 5000;
        private const float MaxFallSpeed = 550;
        private const float GravityAcceleration = 3400;
        private const float JumpLaunchVelocity = -3500.0f;
        private const float JumpControlPower = 0.14f;
        private const float GroundDragFactor = 0.5f;
        private const float AirDragFactor = 0.8f;
        private const float MaxMoveSpeed = 12000;
        private const float MaxJumpTime = 0.35f;

        private bool wasJumping;
        private float jumpTime;

        private float DoJump(float velocityY, float elapsed)
        {
            if (isJumping)
            {
                if ((!wasJumping && isOnGround) || jumpTime > 0.0f)
                {
                    jumpTime += elapsed;
                }

                if (0.0f < jumpTime && jumpTime <= MaxJumpTime)
                {
                    velocityY = JumpLaunchVelocity * (1.0f - (float)Math.Pow(jumpTime / MaxJumpTime, JumpControlPower));
                }
                else
                {
                    jumpTime = 0.0f;
                }
            }
            else
            {
                // Continues not jumping or cancels a jump in progress
                jumpTime = 0.0f;
            }
            wasJumping = isJumping;

            return velocityY;
        }

        private void UpdateInput(float elapsed)
        {
            var rightDown = inputService.KeyboardInput.IsKeyDown(rightKey);
            var leftDown = inputService.KeyboardInput.IsKeyDown(leftKey);
            isJumping = inputService.KeyboardInput.IsKeyDown(jumpKey);

            if (rightDown)
                inputMovement = 1;
            else if (leftDown)
                inputMovement = -1;
            else
                inputMovement = 0;

        }

        public override void Register()
        {
            this.tileGridWorld = this.Owner.SceneOwner.Graph.FindEntity<TileGridEntity>("BlockTiles");
        }

        public void CopyTo(object item)
        {
            var x = item as CharacterControllerComponent;

        }




        public void ApplyPhysics(float elapsed)
        {
            Vector3 previousPosition = Owner.Position;

            // Base velocity is a combination of horizontal movement control and
            // acceleration downward due to gravity.
            velocity.X += inputMovement * MoveAcceleration * elapsed;
            velocity.Y = MathHelper.Clamp(velocity.Y + GravityAcceleration * elapsed, -MaxFallSpeed, MaxFallSpeed);

            velocity.Y = DoJump(velocity.Y, elapsed);

            if (isOnGround)
                velocity.X *= GroundDragFactor;
            else
                velocity.X *= AirDragFactor;

            velocity.X = MathHelper.Clamp(velocity.X, -MaxMoveSpeed, MaxMoveSpeed);

            Owner.Position += velocity * elapsed;
            Owner.Position = new Vector2((float)Math.Round(Owner.Position.X), (float)Math.Round(Owner.Position.Y)).ToVector3();

            // If the player is now colliding with the level, separate them.
            HandleCollisions();

            // If the collision stopped us from moving, reset the velocity to zero.
            if (Owner.Position.X == previousPosition.X)
                velocity.X = 0;

            if (Owner.Position.Y == previousPosition.Y)
                velocity.Y = 0;
        }

    

        
    }
    enum TileCollision
    {
        /// <summary>
        /// A passable tile is one which does not hinder player motion at all.
        /// </summary>
        Passable = 0,

        /// <summary>
        /// An impassable tile is one which does not allow the player to move through
        /// it at all. It is completely solid.
        /// </summary>
        Impassable = 1,

        /// <summary>
        /// A platform tile is one which behaves like a passable tile except when the
        /// player is above it. A player can jump up through a platform as well as move
        /// past it to the left and right, but can not fall down through the top of it.
        /// </summary>
        Platform = 2,
    }
}
